﻿using System;
using System.Diagnostics;
using System.Threading.Tasks;
using Windows.Foundation.Collections;
using Windows.Storage;
using Xiaowei.Helpers;
using Log = Xiaowei.Diagnostics.Debug;
using Debug = Xiaowei.Diagnostics.Debug;
using Network;
using Xiaowei.Settings;
using Windows.UI.Xaml;
using Xiaowei.Players;
using Xiaowei.Players.XiaoweiPlayerLib;
using System.Threading;
using System.Collections.Generic;
using Windows.Data.Json;
using HPMetrics;
using XiaoweiV2;
using Xiaowei.Services;
using Windows.System.Power;
using System.Net;
using System.Runtime.InteropServices;
using Windows.Networking.Connectivity;
using Windows.ApplicationModel.ConversationalAgent;

namespace Xiaowei.Models
{
    public enum ActivateType
    {
        Keyword,
        PushToTalk,
        HotKey,
        Context,
        AutoTest
    }

    public class ApplicationModel
    {
        public delegate void ActivateEventHandler(bool requireAck);
        public delegate void AbortActivateEventHandler();
        public delegate void ApplicationEventHandler();

        private bool isSuspended = false;

        [DllImport("kernel32", CharSet = CharSet.Unicode, SetLastError = true)]
        private static extern bool WritePrivateProfileString(
            string lpAppName, string lpKeyName, string lpString, string lpFileName);



        private static readonly Lazy<ApplicationModel> LazyInstance = new Lazy<ApplicationModel>(() =>
        {
            return new ApplicationModel();
        }, LazyThreadSafetyMode.ExecutionAndPublication);

        public static ApplicationModel Instance { get { return LazyInstance.Value; } }

        private ApplicationModel() 
        {
            //  InitialSubscribeOffLineEvent();
        }


        private bool baseServiceInited=false;

        /// <summary>
        /// Because MVA is the app's base,so must initialize first.
        /// </summary>
        public void InitMvaService()
        {
            if(baseServiceInited)
            {
                return;
            }
            baseServiceInited = true;

            MVAWakeUpService.Instance.WakeupConfirmed += MVAWakeUpService_WakeupConfirmed;
            MVAWakeUpService.Instance.WakeupRejected += MVAWakeUpService_WakeupRejected;
            MVAWakeUpService.Instance.Init();
        }
        public void DoCleanAndExitWork()
        {
            //Process.GetCurrentProcess().Close();
            Application.Current.Exit();
        }
        public void Clear()
        {
        }
        private void MVAWakeUpService_WakeupRejected()
        {
        }

        private void MVAWakeUpService_WakeupConfirmed()
        {
            if (AppState.IsLogin)
            {
                Activate(requireAck:true,activateType: ActivateType.Keyword);
                Log.WriteLine("MVAWakeUpService_WakeupConfirmed,app is login,and will active recognize flow");
            }
        }


        private bool serviceinited = false;

        /// <summary>
        /// When the app is launched(UI is created),the other services will be initalized.
        /// </summary>
        public void InitService()
        {
            if(serviceinited)
            {
                return;
            }
            serviceinited = true;
            var t = Task.Run(() =>
            {
                AppServiceResponseHandlers.AppServiceResponseRegister.RegisterFunctions();
                AppServiceManager.Instance.AppServiceEnable = true;
                RegisterVoiceAIHandlers();
                LicenseService.Instance.FetchLicenseEnded += (success) =>
                {
                    if (!success) return;
                    var sn = LocalSettings.Guid;
                    var signature = LocalSettings.License;
                    var appId = LocalSettings.AppId;
                    var pid = LocalSettings.ProductId;
                    var keyVersion = LocalSettings.KeyVersion;

                    if (!string.IsNullOrEmpty(sn) && !string.IsNullOrEmpty(signature) && !string.IsNullOrEmpty(appId))
                    {
                        XiaoweiAI.Instance.Init(sn, signature, appId, pid, keyVersion, LocalSettings.WelcomePageEnabled);
                    }
                    if (DebugConfig.MetricsEnabled)
                    {
                        InitMetrics();
                    }

                };
                InitMvaService();
                LicenseService.Instance.Init();
                OpenAppService.Init();
                //StartupService.Init();

                Microphone.Init();
                HotkeyService.Instance.Init();
                _ = RegisterHotkey((int)HotkeyId.Wakeup, LocalSettings.HotKey);
                Display.Instance.Init();
                _ = PlayerService.Instance;

                RegisterEventHandlers();

                ApplicationData.Current.LocalSettings.Values["ForegroundProcessId"] = Process.GetCurrentProcess().Id;


                String path = ApplicationData.Current.LocalFolder.Path + "\\xiaowei.ini";
                WritePrivateProfileString("Status", "Display", "null", path);



                Debug.WriteLine("Run app service during init");
            });
            
        }

        public bool IsBackgroundMode { get; set; } = true;

        public bool IsMinimized 
        {
            get;set;
        }

        //public bool IsCovered = false;
        public bool IsCoveredByLock { get; set; } = false;

        private void RegisterVoiceAIHandlers()
        {
            XiaoweiAI.Instance.XiaoweiAIStateChanged += (state) =>
            {
                switch (state)
                {
                    case XiaoweiAI.XiaoweiAIStateEnum.Idle:
                        MVAWakeUpService.Instance.RequestAgentStateChangeAsync( ConversationalAgentState.Inactive);
                        LEDStateManager.RemoveAttachState(LEDStateManager.AttachStateEnum.micOff);
                        LEDStateManager.SetState(LEDStateManager.MainStateEnum.idle);
                        PlayerManager.Instance.Resumed();
                        break;
                    case XiaoweiAI.XiaoweiAIStateEnum.ListeningPrepare:
                        // MVAWakeUpService.Instance.RequestAgentStateChangeAsync(ConversationalAgentState.ListeningAndSpeaking);
                        PlayerManager.Instance.Suspend();
                        PlayPrepareListeninngTTS();
                        break;
                    case XiaoweiAI.XiaoweiAIStateEnum.Listening:
                        MVAWakeUpService.Instance.RequestAgentStateChangeAsync(ConversationalAgentState.Listening);
                        StopTTS();
                        LEDStateManager.SetState(LEDStateManager.MainStateEnum.listening);
                        
                        break;
                    case XiaoweiAI.XiaoweiAIStateEnum.Thinking:
                        MVAWakeUpService.Instance.RequestAgentStateChangeAsync(ConversationalAgentState.Listening);
                        LEDStateManager.SetState(LEDStateManager.MainStateEnum.thinking);
                        break;
                    case XiaoweiAI.XiaoweiAIStateEnum.Speaking:
                        MVAWakeUpService.Instance.RequestAgentStateChangeAsync(ConversationalAgentState.Inactive);
                        LEDStateManager.SetState(LEDStateManager.MainStateEnum.speaking);
                        break;
                    case XiaoweiAI.XiaoweiAIStateEnum.Mute:
                        MVAWakeUpService.Instance.RequestAgentStateChangeAsync(ConversationalAgentState.Inactive);
                        LEDStateManager.SetAttachState(LEDStateManager.AttachStateEnum.micOff);
                        PlayerManager.Instance.Resumed();
                        break;
                    default:
                        break;
                }
                if (state == XiaoweiAI.XiaoweiAIStateEnum.Listening)
                {
                    if (!RecorderService.Instance.Recorder.IsRecording())
                    {
                        RecorderService.Instance.Recorder.Start();
                    }
                }
                else
                {
                    if (RecorderService.Instance.Recorder.IsRecording())
                    {
                        RecorderService.Instance.Recorder.Stop();
                    }
                }
            };
            XiaoweiAI.Instance.IsInitializedChanged += async (state) =>
            {
                if(state)
                {
                    AppState.IsLaunchCompleted = true;
                    Debug.WriteLine("Initialized");
                    // track launch event
                    _ = Metrics.Track(XiaoweiAI.Instance.UserId, (int)XwEventType.Launch, AppState.LaunchType.ToString());
                    // store the timestamp after launched.
                    var t = await Metrics.Timestamp().ConfigureAwait(false);
                    if (t.HasValue)
                    {
                        AppState.LaunchedAt = t.Value;
                    }
                    _=Task.Run(()=> {
                        PlayResponseAndSystemTTS(XiaoweiAI.Instance.RequestTTS(Audios.LoadingDone));
                    });
                    
                }
            };
            XiaoweiAI.Instance.IsLoginChanged += async (state) => {
                if(state)
                {
                    Debug.WriteLine("Login");
                    AppState.IsLogin = true;

                    _ = ServerApi.Instance.LoginRecord(XiaoweiAI.Instance.UserId, LocalSettings.DeviceName);

                    // track login event
                    _ = Metrics.Track(XiaoweiAI.Instance.UserId, (int)XwEventType.Login);

                    //Say welcome to user after scan qrcode.
                    if (XiaoweiAI.Instance.IsInitialized)
                    {
                        _ = Task.Run(() => {
                            PlayResponseAndSystemTTS(XiaoweiAI.Instance.RequestTTS(Audios.Welcome));
                        });
                    }
                    BeginToUse();
                }
                else
                {
                    Debug.WriteLine("Logout");

                    await AbortActivation().ConfigureAwait(false);
                    AppState.IsLogin = false;
                    TTSPlayerService.Instance.Cancel();
                    if (XiaoweiAI.Instance.IsInitialized)
                    {
                        _ = Task.Run(() => {
                            PlayResponseAndSystemTTS(XiaoweiAI.Instance.RequestTTS(Audios.LoginOut));
                        });
                    }
                    //Stop all alarms
                    XiaoweiPlayerManager.Instance.Close();
                    XiaoweiAI.Instance.ClearHistory();
                }
            };
            XiaoweiAI.Instance.OnlineStateChanged += (online) =>
            {
                try
                {
                    if (!online)
                    {
                        TestConnection();
                    }
                }
                catch (Exception ex)
                {
                    Debug.WriteLine($"XiaoweiAI.OnlineStateChanged exception:{ex.ToString()}");
                }

            };
            XiaoweiAI.Instance.OnlineModeChanged += (online) => {
                if(online)
                {
                    PlayResponseAndSystemTTS(Audios.DeviceOnline);
                }
                else
                {
                    PlayResponseAndSystemTTS(Audios.DeviceOffline);
                }
            
            };
            XiaoweiAI.Instance.ResponseErrored += (e) =>
            {
                var isAvailable = XiaoweiAI.Instance.IsOnlineMode;
                if (isAvailable)
                {
                    ProcessSdkResponseError((ResponseError)e);
                }
            };
        }

        private void TestConnection()
        {
            //Try to send a request to hp service to keep online state on.
            _ = Task.Run(() => {
                HttpWebRequest req = (HttpWebRequest)WebRequest.Create(new Uri("https://xiaowei.ext.hp.com/xiaowei/api/v1/health"));
                req.Timeout = 5000;
                try
                {
                    HttpWebResponse res = (HttpWebResponse)req.GetResponse();
                    Debug.WriteLine($"Ping2:{res.StatusCode}");
                }
                catch (Exception)
                {
                    Debug.WriteLine($"Ping2:FAIL");
                }

            });

            var profile = NetworkInformation.GetInternetConnectionProfile();
            if (profile != null)
            {
                Debug.WriteLine($"NetworkConnectivityLevel2 is {profile.GetNetworkConnectivityLevel()}");
            }
            else
            {
                Debug.WriteLine("NetworkConnectivity profile2 is null");
            }
        }

        private void RegisterEventHandlers()
        {

            RecorderService.Instance.Recorder.ErrorOccured += () =>
            {
                
                Debug.WriteLine("ErrorOccured");
                AbortActivation();
            };

            RecorderService.Instance.Recorder.FrameArrived += async(pcm) => 
            {
                await XiaoweiAI.Instance.SendSpeechAudio(pcm).ConfigureAwait(false);

            };
            Connectivity.AvailabilityChanged += async (available) =>
              {
                  Log.WriteLine($"OnNetworkAvailabilityChanged, {available}");
                  if (available)
                  {
                      PlayResponseAndSystemTTS(Audios.NetworkRecovery);


                      //XiaoweiSDK.Instance.ReportState();

                      if (AppState.IsLaunchCompleted)
                      {
                          //check if has binder
                          XiaoweiAI.Instance.CheckNetworkState();
                      }
                  }
                  else
                  {

                      if (XiaoweiAI.Instance.IsOnline)
                      {
                          Debug.WriteLine("Netword is unavailable, however SDK is still online");

                      }
                      if (!AppState.IsLaunchCompleted)
                      {
                          PlayResponseAndSystemTTS(Audios.NetworkHasProblem);
                      }
                      else
                      {
                          PlayResponseAndSystemTTS(Audios.NetworkException);
                      }
                  }
              };

            ServerApi.Instance.ErrorOccured += async(error, message) =>
            {
                Log.WriteLine($"OnServerApiErrorOccured, error:{error}, message:{message}");
                switch (error)
                {
                    //case ServerApi.AppError.RequestFailed:
                    //case ServerApi.AppError.NetworkError:
                    //    break;
                    case ServerApi.AppError.SerialNumberInvalid:
                    case ServerApi.AppError.SerialNumberNotFound:
                    case ServerApi.AppError.SSIDInvalid:
                        PlayResponseAndSystemTTS(Audios.UnsupportedDevice);
                        break;
                    default:break;
                }
            };

            TTSPlayerService.Instance.IsPlayingChanged += (state) => 
            {
                if(state)
                {

                }
                else
                {
                    if(PlayingPrepareSound)
                    {
                        PlayingPrepareSound = false;
                        XiaoweiAI.Instance.PrepareEnd();
                    }
                    else
                    {
                        XiaoweiAI.Instance.SetSpeakingState(false);
                    }
                    
                }
                
            };
        }

        public async Task<HotkeyService.HotkeyRegisterResult> RegisterHotkey(int id,string key)
        {
            LocalSettings.HotKey = key;
            return await HotkeyService.Instance.RegisterHotkeyAsync(id,
                new HotkeyService.HotkeyArgs()
                {
                    Action = HotkeyAction,
                    FsModifiers = Modifiers.Control | Modifiers.Shift | Modifiers.NoRepeat,
                    Key = key
                }).ConfigureAwait(false);
        }

        public void HotkeyAction()
        {
            if(!Settings.LocalSettings.HotKeyEnabled)
            {
                return;
            }
            if(XiaoweiAI.Instance.CanActive)
            {
                this.Activate(requireAck: false, ActivateType.HotKey);
            }
            else
            {
                this.AbortActivation();
            }
            Debug.WriteLine("the ActivationTriggered trigger end.");
        }

        private void UpdateAppList()
        {
            //"{\"appName\": \"战网\",\"appNickName\": [\"暴雪战网\"],\"appId\": \"BLZAPP\"}",
            List<string> apps = new List<string>();
            string logOutput = "\r\n------------------------------APP_List------------------------------";
            foreach (var item in OpenAppService.AppList)
            {
                logOutput += "\r\n" + item;
                JsonObject jo = new JsonObject();
                jo.Add("appName",JsonValue.CreateStringValue(item));
                jo.Add("appNickName", new JsonArray());
                jo.Add("appId", JsonValue.CreateStringValue(item));
                apps.Add(jo.ToString());
            }
            logOutput += "\r\n--------------------------------------------------------------------";
            Log.WriteLine(logOutput);
            var defaultAPPs = ServerApi.Instance.GetAPPs().ConfigureAwait(false).GetAwaiter().GetResult();
            logOutput = "\r\n------------------------------APP_List2-----------------------------";
            foreach (var item in defaultAPPs)
            {
                logOutput += "\r\n" + item;
                JsonObject jo = new JsonObject();
                jo.Add("appName", JsonValue.CreateStringValue(item.Key));
                jo.Add("appNickName", new JsonArray());
                jo.Add("appId", JsonValue.CreateStringValue(item.Value));
                apps.Add(jo.ToString());
            }
            logOutput += "\r\n--------------------------------------------------------------------";
            Log.WriteLine(logOutput);
            XiaoweiAI.Instance.UpdateAppList(apps);
        }

        public static async Task TryReadDebugFileAsync()
        {
            await DebugConfig.LoadFromFileAsync(ApplicationData.Current.LocalFolder, "debug_settings.dat").ConfigureAwait(false);

            Metrics.IsEnabled = DebugConfig.MetricsEnabled;

            LocalSettings.LogEnabled = DebugConfig.LogEnabled;
            Logger.Debug.SaveEnabled = DebugConfig.LogEnabled;

            XiaoweiAI.Instance.SaveSpeechEnabled = DebugConfig.SaveSpeechEnabled;
            XiaoweiAI.Instance.SaveLogEnabled = DebugConfig.TencentConfig.LogEnabled;
            XiaoweiAI.Instance.LogLevel = DebugConfig.TencentConfig.LogLevel;
            XiaoweiAI.Instance.KeywordVerificationEnabled = DebugConfig.TencentConfig.KwsEnabled;

            TTSPlayerService.Instance.SaveTTSEnabled = DebugConfig.SaveTTSEnabled;
        }

        IOpenAppService OpenAppService => ServiceFactory.OpenAppService;
        public void BeginToUse()
        {
            Debug.WriteLine("BeginToUse");
            //Initialize IOT and alarms
            Task.Run(() =>
            {
                XiaoweiAI.Instance.FetchIOTDevices();
                AlarmService.Instance.Begin();
                OpenAppService.InitWaiter.WaitOne();
                UpdateAppList();
            });
            Debug.WriteLine("BeginToUse end");
        }


        public static void InitMetrics()
        {
            var host = LocalSettings.HostAddress;
            var token = new HPMetrics.Token
            {
                Value = ServerApi.Token.Value,
                Type = ServerApi.Token.Type
            };
            var app = new HPMetrics.App
            {
                Id = "1",
                Name = "惠小微",
                Version = AppState.Version.ToString()
            };
            var device = new Device
            {
                Ssid = LocalSettings.SSID,
                Sn = LocalSettings.SerialNumber
            };
            Metrics.Initialize(host, token, app, device);
        }

        private  async void ProcessSdkResponseError(ResponseError err)
        {
            Debug.WriteLine($"ProcessSdkResponseError:{err}");
            switch (err)
            {
                case ResponseError.NotMatchSkill:
                    _ = XiaoweiAI.Instance.RequestTTS(Audios.NotMatchSkill);
                    break;
                case ResponseError.MusicNotLogin:
                    _ = XiaoweiAI.Instance.RequestTTS(Audios.ReLogin);
                    break;
                case ResponseError.MusicNotFound:
                    _ = XiaoweiAI.Instance.RequestTTS(Audios.MusicNotFind);
                    break;
                case ResponseError.MusicNotHasLaw:
                    _ = XiaoweiAI.Instance.RequestTTS(Audios.MusicNotHasLaw);
                    break;
                case ResponseError.MusicNotFoundResult:
                    _ = XiaoweiAI.Instance.RequestTTS(Audios.MusicNotFindResult);
                    break;
                case ResponseError.ErrMsgSendfailed:
                case ResponseError.ErrMsgSendtimeout:
                case ResponseError.ErrAiAudioParseReq:
                case ResponseError.ErrAiAudioEmptyVoiceData:
                case ResponseError.ErrAiAudioVoiceToText:
                case ResponseError.ErrAiAudioTextAnalysis:
                case ResponseError.ErrAiAudioTextToVoice:
                //fix 6684 can't recognize the vocie
                case ResponseError.ErrVoicePackage:
                    PlayResponseAndSystemTTS(Audios.ErrorAboutNetWork);
                    break;
                case ResponseError.ErrConnectionTimeout:
                    Debug.WriteLine("ResponseError.ErrConnectionTimeout");
                    XiaoweiAI.Instance.CheckNetworkState();
                    break;
                default:
                    //fixed bug 6679
                    PlayResponseAndSystemTTS(Audios.Distracted);
                    break;
            }
        }


        public async Task AbortActivation()
        {
            Debug.WriteLine("ApplicationModel.AbortActivation");
            await XiaoweiAI.Instance.AbortRequestAsync().ConfigureAwait(false);
            Debug.WriteLine("ApplicationModel.AbortActivation end");
        }
        bool requireAckTrue = false;
        public void Activate(bool requireAck = true, ActivateType activateType = ActivateType.Keyword)
        {
            Task.Run(()=> {
                if (VersionVerification.Reminder == UpdateReminder.ForceUpdate) return;
                switch (activateType)
                {
                    case ActivateType.Keyword:
                        _ = Metrics.Track(XiaoweiAI.Instance.UserId, (int)XwEventType.Awake, Settings.WakeupType.Voice.ToString());
                        break;
                    case ActivateType.PushToTalk:
                        _ = Metrics.Track(XiaoweiAI.Instance.UserId, (int)XwEventType.Awake, Settings.WakeupType.PushToTalk.ToString());
                        break;
                    case ActivateType.HotKey:
                        _ = Metrics.Track(XiaoweiAI.Instance.UserId, (int)XwEventType.Awake, Settings.WakeupType.Hotkey.ToString());
                        break;
                }
                requireAckTrue = requireAck && activateType == ActivateType.Keyword && Settings.LocalSettings.AckVoiceEnabled;
                XiaoweiAI.Instance.Active(requireAckTrue ? "我在呢" : "");
            });

        }

        private bool PlayingPrepareSound = false;

        /// <summary>
        /// because it must set state in XiaoweiAI service, so must call this function, not immediatly call TTSPlayerService.
        /// </summary>
        /// <param name="url">the url of tts, is url is empty or null, it will set XiaoweiAI state to idle.</param>
        public void PlayResponseAndSystemTTS(string url)
        {
            if (XiaoweiAI.Instance.SetSpeakingState(true))
            {
                if (!TTSPlayerService.Instance.Play(url))
                {
                    XiaoweiAI.Instance.SetSpeakingState(false);
                }
            }
        }
        /// <summary>
        /// Call this function when xiaoweiAI state is prepareListening.
        /// </summary>
        private void PlayPrepareListeninngTTS()
        {
            if(requireAckTrue)
            {

                if (!TTSPlayerService.Instance.Play(Audios.WakeupAck))
                {
                    XiaoweiAI.Instance.PrepareEnd();
                    PlayingPrepareSound = false;
                }
                else
                {
                    PlayingPrepareSound = true;
                }
            }
            else if(XiaoweiAI.Instance.HasContext)
            {
                if (!TTSPlayerService.Instance.Play(Audios.ContextSound))
                {
                    XiaoweiAI.Instance.PrepareEnd();
                    PlayingPrepareSound = false;
                }
                else
                {
                    PlayingPrepareSound = true;
                }
            }
            else
            {
                XiaoweiAI.Instance.PrepareEnd();
                PlayingPrepareSound = false;
            }
            requireAckTrue = false;
        }
        public void StopTTS()
        {
            TTSPlayerService.Instance.Cancel();
            XiaoweiAI.Instance.SetSpeakingState(false);
        }
    }
}
